/*
*	Copyright (C) 2014  Julio Montes, Mario Carrillo
*	
*	Julio Montes e-mail: imc.coder@gmail.com 
*
*	Mario Carrillo e-mail: mario_90ago@live.com.mx
*
*	BSD 3-Clause License
*/

#include "sdk/include/pluginmgr.h"

#include "sdk/include/plugin.h"
#include "sdk/include/filecfg.h"
#include "sdk/include/util.h"
#include "sdk/include/path.h"


#include <wx/dir.h>
#include <wx/dynlib.h>
#include <wx/tokenzr.h>

#include <algorithm>

namespace sdk
{
    template<> PluginManager *sdk::Mgr<PluginManager>::instance = 0;

    bool sortFunction( const pluginData &v1, const pluginData &v2 )
    {
        return v1.priority < v2.priority;
    }

    PluginManager::PluginManager()
    {

    }

    PluginManager::~PluginManager()
    {
        for( size_t i=0; i<m_vectorPlugins.size(); ++i )
        {
            if( m_vectorPlugins[i].plugin )
                delete m_vectorPlugins[i].plugin;
        }
    }

    bool PluginManager::LoadPlugins()
    {
        LOGC( wxT( "Loading plugins..." ) );
        wxArrayString files;

        wxDir::GetAllFiles( sdk::Path::s_dirPlugins, &files, wxT( "*.plg" ), wxDIR_FILES );

        for( size_t i=0; i<files.Count(); ++i )
        {
            sdk::FileConfig fileConfig;
            if( fileConfig.LoadFile( files[i] ) )
            {
                pluginData l_plgdata;
                l_plgdata.library = fileConfig.ReadString( wxT( "/plugin" ), wxT( "" ), wxT( "library") );
                if( !l_plgdata.library.IsEmpty() )
                {
                #ifdef SDK_DEBUG
                    l_plgdata.library = l_plgdata.library.BeforeFirst( '.' ) + wxT( "_debug." ) + l_plgdata.library.AfterFirst( '.' );
                #endif
                    if( wxFileExists( sdk::Path::s_dirPlugins + l_plgdata.library ) )
                    {
                        bool installed = fileConfig.ReadBool( wxT( "/plugin" ), true, wxT( "enabled" ) );
                        if( installed )
                        {
                            l_plgdata.loaded = false;
                            l_plgdata.plugin = NULL;
                            l_plgdata.priority = 0;
                            l_plgdata.name = files[i].AfterLast( wxT( '/' ) ).BeforeLast( wxT( '.' ) );
                            wxStringTokenizer st( fileConfig.ReadString( wxT( "/plugin" ), wxT( "" ), wxT( "dependencies" ) ), wxT( "," ), wxTOKEN_STRTOK );
                            while( st.HasMoreTokens() )
                            {
                                l_plgdata.dependencies.Add( st.GetNextToken() );
                            }

                            m_vectorPlugins.push_back( l_plgdata );
                        }
                    }
                    else
                    {
                        //TODO: show meesage error, library not found
                    }
                }
                else
                {
                    //TODO: show message error, the file has not library
                }
            }
            else
            {
                //TODO: maybe show error
            }
        }

        //calculate the priority of plugins
        for( size_t i=0; i<m_vectorPlugins.size(); ++i )
        {
            //if the priority has not calculated
            if( m_vectorPlugins[i].priority == 0 )
            {
                if( !CalculatePriority( &m_vectorPlugins[i] ) )
                {
                    m_vectorPlugins.erase( m_vectorPlugins.begin() + i );
                    --i;
                }
            }
        }
        //sort the plugins by priority
        std::sort( m_vectorPlugins.begin(), m_vectorPlugins.end(), sortFunction );

        LoadLibraries();

        return true;
    }
    const std::vector<pluginData> &PluginManager::GetPlugins() const
    {
        return m_vectorPlugins;
    }


    void PluginManager::LoadLibraries()
    {
        for( size_t i=0; i<m_vectorPlugins.size(); ++i )
        {
            if( CheckDependencies( m_vectorPlugins[i] ) )
            {
                const wxString &library = m_vectorPlugins[i].library;

                wxDynamicLibrary dll;
                dll.Load( sdk::Path::s_dirPlugins + library );

                if( dll.IsLoaded() )
                {
                    wxDYNLIB_FUNCTION( CreatePluginFunction, CreatePlugin, dll );

                    if( pfnCreatePlugin )
                    {
                        sdk::Plugin *plugin = pfnCreatePlugin();
                        LOGC( wxT( "Loading " ) + m_vectorPlugins[i].name + wxT( "..." ) );
                        if( plugin->OnInit() )
                        {
                            dll.Detach();
                            m_vectorPlugins[i].loaded = true;
                            m_vectorPlugins[i].plugin = plugin;
                        }
                        else
                        {
                            sdk::errorDialog( wxT( "Load Plugin" ),
                                wxT( "Cannot initialize the plugin: " ) + sdk::Path::s_dirPlugins + library,
                                wxT( "Failed to initialize the plugin" ),
                                wxT( "\n1. Reinstall or recompile the plugin: " ) + library );
                        }
                    }
                    else
                    {
                        sdk::errorDialog( wxT( "Load Plugin" ),
                            wxT( "The plugin: " ) + sdk::Path::s_dirPlugins + library + wxT( " not is compatible" ),
                            wxT( "Failed to load the plugin" ),
                            wxT( "\n1. Reinstall or recompile the plugin: " ) + library );
                    }
                }
            }
            else
            {
                //TODO: show message error, the dependencies of plugins, not been loaded
            }
        }
    }

    bool PluginManager::CheckDependencies( const pluginData &plugin )
    {
        for( size_t i=0; i<plugin.dependencies.Count(); ++i )
        {
            pluginData *plg = FindPlugin( plugin.dependencies[i] );
            if( plg != NULL )
            {
                if( !plg->loaded )
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }

        return true;
    }

    bool PluginManager::CalculatePriority( pluginData *plugin )
    {
        for( size_t i=0; i<plugin->dependencies.Count(); ++i )
        {
            pluginData *plg = FindPlugin( plugin->dependencies[i] );
            if( plg != NULL )
            {
                if( plg->priority == 0 )
                {
                    if( !CalculatePriority( plg ) )
                    {
                        return false;
                    }
                }
                plugin->priority += plg->priority;
            }
            else
            {
                //TODO: show mesage error, plugin not found
                return false;
            }
        }
        ++plugin->priority;
        return true;
    }

    pluginData *PluginManager::FindPlugin( const wxString &name )
    {
        for( size_t i=0; i<m_vectorPlugins.size(); ++i )
        {
            if( m_vectorPlugins[i].name == name )
            {
                return &m_vectorPlugins[i];
            }
        }
        return NULL;
    }
}
